package org.apache.catalina.session;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Loader;
import org.apache.catalina.Manager;
import org.apache.catalina.util.CustomObjectInputStream;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;

import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisSentinelPool;
import redis.clients.jedis.exceptions.JedisException;

public class JedisUtil {
	private static final Log log = LogFactory.getLog(JedisUtil.class);
	static ClassLoader classLoader = null;

	public static<V> V exec(JedisPool pool, JedisSentinelPool sentinel, JedisCluster cluster, Callback<V> callback) {
		if(pool != null) {
			return exec(pool, callback);
		}else if(cluster != null) {
			return exec(cluster, callback);
		}else if(sentinel != null) {
			return exec(sentinel, callback);
		}else {
			return null;
		}
	}
	
	public static void close(JedisPool pool, JedisSentinelPool sentinel, JedisCluster cluster) {
		if(pool != null) {
			pool.close();
		}else if(cluster != null) {
			try {
				cluster.close();
			}catch(Exception e) {
				log.debug(e.getMessage());
			}
		}else if(sentinel != null) {
			sentinel.close();
		}
	}
	
	public static<V> V exec(JedisPool pool, Callback<V> callback) {
		Jedis jedis = pool.getResource();
		try {
			return callback.execute(jedis);
		}catch(JedisException je){
			jedis.close();
			jedis = pool.getResource();
			try{
				return callback.execute(jedis);
			}catch(Exception e) {
				log.debug(e.getMessage());
			}
		}catch(Exception e){
			log.debug(e.getMessage());
		}finally {
			jedis.close();
		}
		return null;
	}
	
	public static<V> V exec(JedisSentinelPool sentinel, Callback<V> callback) {
		Jedis jedis = sentinel.getResource();
		try {
			return callback.execute(jedis);
		}catch(JedisException je){
			jedis.close();
			jedis = sentinel.getResource();
			try{
				return callback.execute(jedis);
			}catch(Exception e) {
				log.debug(e.getMessage());
			}
		}catch(Exception e){
			log.debug(e.getMessage());
		}finally {
			jedis.close();
		}
		return null;
	}
	
	public static<V> V exec(JedisCluster cluster, Callback<V> callback) {
		Jedis jedis = new JedisWrapper(cluster);
		try {
			return callback.execute(jedis);
		}catch(JedisException je){
			try{
				return callback.execute(jedis);
			}catch(Exception e) {
				log.debug(e.getMessage());
			}
		}catch(Exception e){
			log.debug(e.getMessage());
		}finally {
			jedis.close();
		}
		return null;
	}
	
	public static byte[] serialize(StandardSession session) {
		try{
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(bos));
			session.writeObjectData(oos);
			oos.close();
			return bos.toByteArray();
		}catch(Exception e) {
			log.debug(e.getMessage());
			return null;
		}
	}
	
	public static StandardSession deserialize(StandardSession session, byte[] bs) {
		try {
			BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(bs));
			CustomObjectInputStream ois = new CustomObjectInputStream(bis, classLoader);
			session.readObjectData(ois);
			return session;
		}catch(Exception e) {
			log.debug(e.getMessage());
			return null;
		}
	}
	
	public static String firstNonBlank(String ... strs) {
		if(strs==null || strs.length==0) {
			return null;
		}
		for(String str : strs) {
			if(!isBlank(str)) {
				return str.trim();
			}
		}
		return "";
	}
	
	public static Set<HostAndPort> nodes(String hostAndPorts) {
		if(!JedisUtil.isBlank(hostAndPorts)) {
			Set<HostAndPort> nodes = new HashSet<HostAndPort>();
			for(String hostAndPort : hostAndPorts.split("[,]")) {
				if(!JedisUtil.isBlank(hostAndPort)) {
					String[] split = hostAndPort.split("[:]");
					if(split!=null && split.length==2) {
						nodes.add(new HostAndPort(split[0], Integer.parseInt(split[1])));
					}else {
						log.debug("bad HostAndPort="+hostAndPort);
					}
				}
			}
			return nodes;
		}
		return null;
	}
	
	public static Set<String> sentinels(Set<HostAndPort> nodes) {
		Set<String> sentinels = new HashSet<String>();
		for(HostAndPort hostAndPort : nodes) {
			sentinels.add(hostAndPort.toString());
		}
		return sentinels;
	}
	
	public static Container getContainer(Manager manager) {
		try {
			return (Container)Manager.class.getMethod("getContext").invoke(manager);
		}catch(Exception e) {
			log.debug(e.getMessage());
			try {
				return (Container)Manager.class.getMethod("getContainer").invoke(manager);
			}catch(Exception ex) {
				log.info(ex.getMessage());
				return null;
			}
		}
	}
	
	public static void setClassLoader(Manager manager) {
		try {
			//8.0.*+ getContext().getLoader()
			Object context = Manager.class.getMethod("getContext").invoke(manager);
			Loader loader = (Loader)Context.class.getMethod("getLoader").invoke(context);
			classLoader = loader.getClassLoader();
		}catch(Exception e) {
			log.debug(e.getMessage());
			try {
				//7.0.* getContainer().getLoader()
				Object container = Manager.class.getMethod("getContainer").invoke(manager);
				Loader loader = (Loader)Container.class.getMethod("getLoader").invoke(container);
				classLoader = loader.getClassLoader();
			}catch(Exception ex) {
				log.info(ex.getMessage());
			}
		}
	}
	
	public static boolean isBlank(String str) {
		return str==null || str.trim().length()==0;
	}
	
	public interface Callback<V> {
		V execute(Jedis jedis);
		
		public static class DELETE implements Callback<Boolean> {
			byte[] key = null;
			public DELETE(String id) { key = id.getBytes(); }
			public Boolean execute(Jedis jedis) {
				return 1==jedis.del(key);
			}
		}
		
		public static class FLUSHDB implements Callback<String> {
			public String execute(Jedis jedis) {
				return jedis.flushDB();
			}
		}
		
		public static class DBSIZE implements Callback<Long> {
			public Long execute(Jedis jedis) {
				return jedis.dbSize();
			}
		}
		
		public static class KEYS implements Callback<Set<String>> {
			String pattern = null;
			public KEYS(String pattern) { this.pattern = pattern; }
			public Set<String> execute(Jedis jedis) {
				return jedis.keys(pattern);
			}
		}
	}
	
	static class JedisWrapper extends Jedis {
		JedisCluster cluster;
		JedisWrapper(JedisCluster cluster) { this.cluster = cluster; }
		public byte[] get(byte[] key) { return cluster.get(key); }
		public Long del(byte[] key) { return cluster.del(key); }
		public String setex(byte[] key, int seconds, byte[] value) { return cluster.setex(key, seconds, value); }
		public Long expire(String key, int seconds) { return cluster.expire(key, seconds); }
		public String flushDB() {
			Collection<JedisPool> pools = cluster.getClusterNodes().values();
			Callback.FLUSHDB flushdb = new Callback.FLUSHDB();
			for(JedisPool pool : pools) {
				exec(pool, flushdb);
			}
			return "OK";
		}
		public Long dbSize() {
			long size = 0L;
			Collection<JedisPool> pools = cluster.getClusterNodes().values();
			Callback.DBSIZE dbSize = new Callback.DBSIZE();
			for(JedisPool pool : pools) {
				size += exec(pool, dbSize);
			}
			return size;
		}
		public Set<String> keys(String pattern) {
			Set<String> keySet = new HashSet<String>();
			Collection<JedisPool> pools = cluster.getClusterNodes().values();
			Callback.KEYS keys = new Callback.KEYS(pattern);
			for(JedisPool pool : pools) {
				keySet.addAll(exec(pool, keys));
			}
			return keySet;
		}
		public void close() { }
	}
}
